﻿
#region 作者和版权
/*************************************************************************************
* CLR 版本: 4.0.30319.42000
* 类 名 称: TcpServer
* 机器名称: SUNTIA-QL
* 命名空间: ServerFramework.Net.Tcp
* 文 件 名: TcpServer
* 创建时间: 2015/9/23 10:47:04
* 作 者: 乔龙 Qiaolong
* 版 权: TcpServer说明：本代码版权归乔龙所有，使用时必须带上乔龙网站地址 All Rights Reserved (C) 2015 - 2013
* 签 名: To be or not, it is not a problem !
* 网 站: http://www.suntia.net
* 邮 箱: qiaolong306@qq.com 
* 唯一标识： baf1110a-cb8c-4b68-a118-8e65f77d17ec 
*
* 登录用户: simida
* 所 属 域: SUNTIA-QL

* 创建年份: 2015
* 修改时间:
* 修 改 人:
* 
************************************************************************************/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ServerFramework.Tools;
using ServerFramework.Net.Msg;
using System.Collections.Concurrent;


namespace ServerFramework.Net.Tcp
{
    /// <summary>
    /// tcp服务器
    /// </summary>
    public class TcpServer
    {
        public Action<UserToken> onClientConn;              //当用户连接
        public Action<UserToken,string> onClientClose;             //当用户关闭
        public Action<MsgPacket> onRecvMessage;     //当收到消息




        /// <summary>
        /// 监听socket
        /// </summary>
        private Socket _server;
        private int _maxClient;//最大客户端连接数
        private Semaphore _acceptClients;
        private UserTokenPool _pool;

        /// <summary>
        /// 在线的用户
        /// </summary>
        private ConcurrentDictionary<int, UserToken> _onlineTokens = new ConcurrentDictionary<int, UserToken>();

        private int _connNum = 0;


        /// <summary>
        /// 心跳检测计时器
        /// </summary>
        private System.Timers.Timer _heartTimer;

         /// <summary>
       /// 初始化通信监听
       /// </summary>
       /// <param name="port">监听端口</param>
        public TcpServer(int max)
        {
           //实例化监听对象
            _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
           //设定服务器最大连接人数
           _maxClient = max;
           //创建连接池
           _pool = new UserTokenPool(_maxClient);
            
           //启用心跳检测计时器
           _heartTimer = new System.Timers.Timer(30000);
           _heartTimer.Elapsed += _heartTimer_Elapsed;
           _heartTimer.Start();

           //连接信号量
           _acceptClients = new Semaphore(_maxClient, _maxClient);
           for (int i = 0; i < _maxClient; i++)
           {
               UserToken token = new UserToken();
               token.server = this;
               token.linkId = i + 10;
               //初始化token信息               
               token.receiveSAEA.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Comleted);
               token.sendSAEA.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Comleted);
               _pool.push(token);
           }
       }

        void _heartTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            ///把超时的用户从在线列表中移除
            List<UserToken> onlines = new List<UserToken>(_onlineTokens.Values);
            foreach (var ut in onlines)
            {
                ///大于30秒 说明断了
                if ((DateTime.Now - ut.ActiveTime).TotalSeconds >= 30)
                {
                    ClientClose(ut, "心跳包超时");
                }
            }
        }


        public void Start(int port, string ip = "")
        {
            IPEndPoint point = null;
            if (string.IsNullOrEmpty(ip))
            {
                point = new IPEndPoint(IPAddress.Any, port);
            }
            else
            {
                point = new IPEndPoint(IPAddress.Parse(ip), port);
            }
            //监听当前服务器网卡所有可用IP地址的port端口
            // 外网IP  内网IP192.168.x.x 本机IP一个127.0.0.1
            try
            {
                _server.Bind(point);
                //置于监听状态
                _server.Listen(10);
                StartAccept(null);
                Log.WriteLog(ELogLevel.LOG_INFO, string.Format("socket listen port: {0} success", port));
            }
            catch (Exception e)
            {
                Log.WriteLog(ELogLevel.LOG_ERROR, e);
            }
        }

        /// <summary>
        /// 开始客户端连接监听
        /// </summary>
        public void StartAccept(SocketAsyncEventArgs e)
        {
            //如果当前传入为空  说明调用新的客户端连接监听事件 否则的话 移除当前客户端连接
            if (e == null)
            {
                e = new SocketAsyncEventArgs();
                e.Completed += new EventHandler<SocketAsyncEventArgs>(Accept_Comleted);
            }
            else
            {
                e.AcceptSocket = null;
            }
            //信号量-1
            _acceptClients.WaitOne();
            try
            {
                bool result = _server.AcceptAsync(e);
                //判断异步事件是否挂起  没挂起说明立刻执行完成  直接处理事件 否则会在处理完成后触发Accept_Comleted事件
                if (!result)
                {
                    ProcessAccept(e);
                }
            }
            catch (Exception)
            {

            }
        }

        public void Accept_Comleted(object sender, SocketAsyncEventArgs e)
        {
            ProcessAccept(e);
        }

        public void ProcessAccept(SocketAsyncEventArgs e)
        {
            //从连接对象池取出连接对象 供新用户使用
            UserToken token = _pool.pop();
            if (token == null)
            {
                e.AcceptSocket.Close();
                Log.WriteLog(ELogLevel.LOG_ERROR, "连接池满了");
                return;
            }
            token.conn = e.AcceptSocket;
            //TODO 通知应用层 有客户端连接
            Interlocked.Increment(ref _connNum);
            _onlineTokens.TryAdd(token.linkId, token);
            Log.WriteLog(ELogLevel.LOG_INFO, string.Format("{0} in,num = {1}", token.conn.RemoteEndPoint, _connNum));
            if (onClientConn != null)
            {
                onClientConn(token);
            }
            //开启消息到达监听
            StartReceive(token);
            //释放当前异步对象
            StartAccept(e);
        }

        public void StartReceive(UserToken token)
        {
            try
            {
                //用户连接对象 开启异步数据接收
                bool result = token.conn.ReceiveAsync(token.receiveSAEA);
                //异步事件是否挂起
                if (!result)
                {
                    ProcessReceive(token.receiveSAEA);
                }
            }
            catch (Exception e)
            {
                Log.WriteLog(ELogLevel.LOG_ERROR, e);
            }
        }

        public void IO_Comleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.LastOperation == SocketAsyncOperation.Receive)
            {
                ProcessReceive(e);
            }
            else
            {
                ProcessSend(e);
            }
        }

        public void ProcessReceive(SocketAsyncEventArgs e)
        {
            try
            {
                UserToken token = e.UserToken as UserToken;
                //判断网络消息接收是否成功
                if (token.receiveSAEA.BytesTransferred > 0 && token.receiveSAEA.SocketError == SocketError.Success)
                {
                    byte[] message = new byte[token.receiveSAEA.BytesTransferred];
                    //将网络消息拷贝到自定义数组
                    Buffer.BlockCopy(token.receiveSAEA.Buffer, 0, message, 0, token.receiveSAEA.BytesTransferred);
                    //处理接收到的消息
                    token.ActiveTime = DateTime.Now;
                    token.Recv(message);
                    StartReceive(token);
                }
                else
                {
                    if (token.receiveSAEA.SocketError != SocketError.Success)
                    {
                        ClientClose(token, token.receiveSAEA.SocketError.ToString());
                    }
                    else
                    {
                        ClientClose(token, "客户端主动断开连接");
                    }
                }
            }
            catch (Exception ex)
            {
                Log.WriteLog(ELogLevel.LOG_ERROR, ex);
            }
        }
        public void ProcessSend(SocketAsyncEventArgs e)
        {
            try
            {
                UserToken token = e.UserToken as UserToken;
                if (e.SocketError != SocketError.Success)
                {
                    ClientClose(token, e.SocketError.ToString());
                }
                else
                {
                    //消息发送成功，回调成功
                    token.Writed();
                }
            }
            catch (Exception) { }
        }

        /// <summary>
        /// 客户端断开连接
        /// </summary>
        /// <param name="token"> 断开连接的用户对象</param>
        /// <param name="error">断开连接的错误编码</param>
        public void ClientClose(UserToken token, string error)
        {
            lock (token)
            {
                if (token.conn != null)
                {
                    //通知应用层面 客户端断开连接了
                    if (onClientClose != null)
                    {
                        onClientClose(token, error);
                    }
                    Interlocked.Decrement(ref _connNum);
                    Log.WriteLog(ELogLevel.LOG_INFO, string.Format("{0} out,num = {1}", token.conn.RemoteEndPoint, _connNum));
                    token.Close();
                    //加回一个信号量，供其它用户使用
                    UserToken temp = null;
                    _onlineTokens.TryRemove(token.linkId, out temp);
                    _pool.push(token);
                    _acceptClients.Release();
                }

            }
        }

        public void Close()
        {
            List<UserToken> tokens = new List<UserToken>(_onlineTokens.Values);
            foreach (var t in tokens)
            {
                ClientClose(t,"服务器主动关闭");
            }
        }

    }
}


